package drds.connection_pool.util.clock;

import java.util.concurrent.TimeUnit;

import static java.util.concurrent.TimeUnit.*;

/**
 * A resolution-independent provider of current time-stamps and elapsed time
 * calculations.
 *
 * @author Brett Wooldridge
 */
public interface Clock
{
   static Clock clock = ClockFactory.create();
   TimeUnit[] TIMEUNITS_DESCENDING = {DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS};
   String[] TIMEUNIT_DISPLAY_VALUES = {"ns", "µs", "ms", "s", "m", "h", "d"};

   /**
    * Get the current time-stamp (resolution is opaque).
    *
    * @return the current time-stamp
    */
   static long currentTime()
   {
      return clock.currentTime0();
   }

   /**
    * Convert an opaque time-stamp returned by currentTime() into
    * milliseconds.
    *
    * @param time an opaque time-stamp returned by an instance of this class
    * @return the time-stamp in milliseconds
    */
   static long toMillis(long time)
   {
      return clock.toMillis0(time);
   }

   /**
    * Convert an opaque time-stamp returned by currentTime() into
    * nanoseconds.
    *
    * @param time an opaque time-stamp returned by an instance of this class
    * @return the time-stamp in nanoseconds
    */
   static long toNanos(long time)
   {
      return clock.toNanos0(time);
   }

   /**
    * Convert an opaque time-stamp returned by currentTime() into an
    * elapsed time in milliseconds, based on the current instant in time.
    *
    * @param startTime an opaque time-stamp returned by an instance of this class
    * @return the elapsed time between startTime and now in milliseconds
    */
   static long elapsedMillis(long startTime)
   {
      return clock.elapsedMillis0(startTime);
   }

   /**
    * Get the difference in milliseconds between two opaque time-stamps returned
    * by currentTime().
    *
    * @param startTime an opaque time-stamp returned by an instance of this class
    * @param endTime   an opaque time-stamp returned by an instance of this class
    * @return the elapsed time between startTime and endTime in milliseconds
    */
   static long elapsedMillis(long startTime, long endTime)
   {
      return clock.elapsedMillis0(startTime, endTime);
   }

   /**
    * Convert an opaque time-stamp returned by currentTime() into an
    * elapsed time in milliseconds, based on the current instant in time.
    *
    * @param startTime an opaque time-stamp returned by an instance of this class
    * @return the elapsed time between startTime and now in milliseconds
    */
   static long elapsedNanos(long startTime)
   {
      return clock.elapsedNanos0(startTime);
   }

   /**
    * Get the difference in nanoseconds between two opaque time-stamps returned
    * by currentTime().
    *
    * @param startTime an opaque time-stamp returned by an instance of this class
    * @param endTime   an opaque time-stamp returned by an instance of this class
    * @return the elapsed time between startTime and endTime in nanoseconds
    */
   static long elapsedNanos(long startTime, long endTime)
   {
      return clock.elapsedNanos0(startTime, endTime);
   }

   /**
    * Return the specified opaque time-stamp plus the specified number of milliseconds.
    *
    * @param time   an opaque time-stamp
    * @param millis milliseconds to add
    * @return a new opaque time-stamp
    */
   static long plusMillis(long time, long millis)
   {
      return clock.plusMillis0(time, millis);
   }

   /**
    * Get the TimeUnit the Clock is denominated in.
    *
    * @return
    */
   static TimeUnit getSourceTimeUnit()
   {
      return clock.getSourceTimeUnit0();
   }

   /**
    * Get a String representation of the elapsed time in appropriate magnitude terminology.
    *
    * @param startTime an opaque time-stamp
    * @param endTime   an opaque time-stamp
    * @return a string representation of the elapsed time interval
    */
   static String elapsedDisplayString(long startTime, long endTime)
   {
      return clock.elapsedDisplayString0(startTime, endTime);
   }

   long currentTime0();

   long toMillis0(long time);

   long toNanos0(long time);

   long elapsedMillis0(long startTime);

   long elapsedMillis0(long startTime, long endTime);

   long elapsedNanos0(long startTime);

   long elapsedNanos0(long startTime, long endTime);

   long plusMillis0(long time, long millis);

   TimeUnit getSourceTimeUnit0();

   default String elapsedDisplayString0(long startTime, long endTime)
   {
      long elapsedNanos = elapsedNanos0(startTime, endTime);

      StringBuilder sb = new StringBuilder(elapsedNanos < 0 ? "-" : "");
      elapsedNanos = Math.abs(elapsedNanos);

      for (TimeUnit unit : TIMEUNITS_DESCENDING)
      {
         long converted = unit.convert(elapsedNanos, NANOSECONDS);
         if (converted > 0)
         {
            sb.append(converted).append(TIMEUNIT_DISPLAY_VALUES[unit.ordinal()]);
            elapsedNanos -= NANOSECONDS.convert(converted, unit);
         }
      }

      return sb.toString();
   }

}
